package engine.stars;

import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Vector;

import engine.math.MT19937;

/**
 * Класс генератора звезд с заданным вероятностным распределением. Предоставляет
 * инструментарий для генерации параметров звезд.
 *
 * @author Stas Kirichok, Oleksij Golenko
 */
public class StarGenerator {
	/* Экземпляр класса. */
	private static StarGenerator instance = new StarGenerator();
	/* ГПСЧ. */
	private static MT19937 prng;
	/* Возможные имена звезд в случайном порядке. */
	private static Vector<String> mixedNames;
	/* Счетчик "безымянных" звёзд. */
	private static int counter = 0;

	/**
	 * Конструктор класса.
	 */
	private StarGenerator() {
		prng = new MT19937();
		mixedNames = mixNames(loadNames());
	}

	/**
	 * Возвращает экземпляр класса.
	 *
	 * @return экземпляр класса
	 */
	public static StarGenerator getInstance() {
		return instance;
	}

	/**
	 * Генерирует звезду случайного типа с уникальным именем.
	 *
	 * @return сгенерированная звезда
	 */
	public Star createNewStar() {
		Star newStar;
		double randNum = prng.nextDouble();

		if (randNum <= 0.1) {
			newStar = new GiantStar();
		} else if (randNum <= 0.9) {
			newStar = new RegularStar();
		} else {
			newStar = new DwarfStar();
		}

		return newStar;
	}

	/**
	 * Генерирует количество планет, вращающихся вокруг звезды.
	 *
	 * @return количество планет
	 */
	protected int generatePlanetsQuantity() {
		int numberOfPlanets;
		MT19937 mt = new MT19937();
		double randNum = mt.nextDouble();
		if (randNum <= 0.15) {
			numberOfPlanets = 0;
		} else if (randNum <= 0.4) {
			numberOfPlanets = 1;
		} else if (randNum <= 0.65) {
			numberOfPlanets = 2;
		} else if (randNum <= 0.8) {
			numberOfPlanets = 3;
		} else if (randNum <= 0.9) {
			numberOfPlanets = 4;
		} else if (randNum <= 0.95) {
			numberOfPlanets = 5;
		} else {
			numberOfPlanets = 6;
		}

		return numberOfPlanets;
	}

	/**
	 * Генерирует случайный спектральный класс звезды.
	 *
	 * @return спектральный класс звезды
	 */
	protected int generateSpectralClass() {
		double randNum = prng.nextDouble();

		if (randNum <= 0.3) {
			return Star.SPECTRAL_CLASS_M;

		} else if (randNum <= 0.5) {
			return Star.SPECTRAL_CLASS_K;

		} else if (randNum <= 0.7) {
			return Star.SPECTRAL_CLASS_G;

		} else if (randNum <= 0.82) {
			return Star.SPECTRAL_CLASS_F;

		} else if (randNum <= 0.92) {
			return Star.SPECTRAL_CLASS_A;

		} else if (randNum <= 0.97) {
			return Star.SPECTRAL_CLASS_B;

		} else {
			return Star.SPECTRAL_CLASS_O;
		}
	}

	/**
	 * Генерирует случайный цвет звезды.
	 * 
	 * @return цвет звезды
	 */
	protected int generateColor() {
		switch (prng.nextInt(7)) {
			case 0:
				return Star.COLOR_RED;
			case 1:
				return Star.COLOR_ORANGE;
			case 2:
				return Star.COLOR_YELLOW;
			case 3:
				return Star.COLOR_YELLOW_WHITE;
			case 4:
				return Star.COLOR_WHITE;
			case 5:
				return Star.COLOR_WHITE_BLUE;
			case 6:
				return Star.COLOR_BLUE;
			default:
				return 0;
		}
	}

	/**
	 * Генерирует цвет звезды по ее спектральному классу.
	 *
	 * @param spectClass спектральный класс звезды
	 * @return цвет звезды
	 * @throws IllegalArgumentException спектральный класс звезды задан не верно
	 */
	protected int generateColor(int spectClass) throws IllegalArgumentException {
		switch (spectClass) {
			case Star.SPECTRAL_CLASS_M:
				return Star.COLOR_RED;

			case Star.SPECTRAL_CLASS_K:
				return Star.COLOR_ORANGE;

			case Star.SPECTRAL_CLASS_G:
				return Star.COLOR_YELLOW;

			case Star.SPECTRAL_CLASS_F:
				return Star.COLOR_YELLOW_WHITE;

			case Star.SPECTRAL_CLASS_A:
				return Star.COLOR_WHITE;

			case Star.SPECTRAL_CLASS_B:
				return Star.COLOR_WHITE_BLUE;

			case Star.SPECTRAL_CLASS_O:
				return Star.COLOR_BLUE;

			default:
				throw new IllegalArgumentException();
		}
	}

	/**
	 * Генерирует светимость звезды по ее спектральному классу.
	 *
	 * @param spectClass спектральный класс звезды
	 * @return светимость звезды
	 * @throws IllegalArgumentException спектральный класс звезды задан не верно
	 */
	protected int generateLuminocity(int spectClass) throws IllegalArgumentException {
		switch (spectClass) {
			case Star.SPECTRAL_CLASS_M:
				return Star.LUMINOCITY_DIM;

			case Star.SPECTRAL_CLASS_K:
				return Star.LUMINOCITY_NORMAL;

			case Star.SPECTRAL_CLASS_G:
				return Star.LUMINOCITY_NORMAL;

			case Star.SPECTRAL_CLASS_F:
				return Star.LUMINOCITY_NORMAL;

			case Star.SPECTRAL_CLASS_A:
				return Star.LUMINOCITY_BRIGHT;

			case Star.SPECTRAL_CLASS_B:
				return Star.LUMINOCITY_VERY_BRIGHT;

			case Star.SPECTRAL_CLASS_O:
				return Star.LUMINOCITY_VERY_BRIGHT;

			default:
				throw new IllegalArgumentException();
		}
	}

	/**
	 * Генерирует размер звезды по ее спектральному классу.
	 *
	 * @param spectClass спектральный класс звезды
	 * @return размер звезды
	 * @throws IllegalArgumentException спектральный класс звезды задан не верно
	 */
	protected double generateSize(int spectClass) throws IllegalArgumentException {
		switch (spectClass) {
			case Star.SPECTRAL_CLASS_M:
				return 0.4;

			case Star.SPECTRAL_CLASS_K:
				return 0.9;

			case Star.SPECTRAL_CLASS_G:
				return 1.1;

			case Star.SPECTRAL_CLASS_F:
				return 1.3;

			case Star.SPECTRAL_CLASS_A:
				return 2.1;

			case Star.SPECTRAL_CLASS_B:
				return 7;

			case Star.SPECTRAL_CLASS_O:
				return 15;

			default:
				throw new IllegalArgumentException();
		}
	}

	/**
	 * Генерирует температуру звезды по ее спектральному классу.
	 *
	 * @param spectClass спектральный класс звезды
	 * @return температура звезды
	 * @throws IllegalArgumentException спектральный класс звезды задан не верно
	 */
	protected int generateTemperature(int spectClass) throws IllegalArgumentException {
		switch (spectClass) {
			case Star.SPECTRAL_CLASS_M:
				return generateTemperature(2000, 3500);

			case Star.SPECTRAL_CLASS_K:
				return generateTemperature(3500, 5000);

			case Star.SPECTRAL_CLASS_G:
				return generateTemperature(5000, 6000);

			case Star.SPECTRAL_CLASS_F:
				return generateTemperature(6000, 7500);

			case Star.SPECTRAL_CLASS_A:
				return generateTemperature(7500, 10000);

			case Star.SPECTRAL_CLASS_B:
				return generateTemperature(10000, 30000);

			case Star.SPECTRAL_CLASS_O:
				return generateTemperature(30000, 60000);

			default:
				throw new IllegalArgumentException();
		}
	}

	/**
	 * Генерирует температуру звезды в заданных пределах.
	 *
	 * @param min нижний предел температуры
	 * @param max верхний предел температуры
	 * @return температура звезды
	 */
	private static int generateTemperature(int min, int max) {
		return (prng.nextInt((max - min) / 100 + 1)) * 100 + min;
	}

	/**
	 * Генерирует уникальное имя звезды.
	 * <p>
	 * Возвращает уникальное локализированное имя звезды, загруженное из файла
	 * или nameModifier + уникальный идентификатор в случае, если
	 * локализированных имен больше не осталось
	 *
	 * @param nameModifier модификатор имени
	 * @return уникальное имя звезды
	 */
	public String generateUniqueName(String nameModifier) {
		String name = null;

		if (!mixedNames.isEmpty()) {
			name = mixedNames.get(0);
			mixedNames.remove(0);

		} else {
			name = nameModifier + counter;
			counter++;
		}

		return name;
	}

	/**
	 * Располагает названия звезд в случайном порядке.
	 *
	 * @param names исходный вектор с названиями звезд
	 * @return вектор с перемешанными названиями звезд
	 */
	private static Vector<String> mixNames(Vector<String> names) {
		Vector<String> mixedNames = new Vector<String>();

		if (names != null) {
			while (!names.isEmpty()) {
				int rnd = prng.nextInt(names.size());

				mixedNames.add(names.get(rnd));
				names.remove(rnd);
			}
		}

		return mixedNames;
	}

	/**
	 * Загружает из файла уникальные ключи локализированных имен.
	 *
	 * @return вектор с уникальными ключами или null, если не удалось загрузить
	 *         файл локализации.
	 * @throws MissingResourceException файл ресурсов для локализации не найден
	 */
	private static Vector<String> loadNames() {
		Vector<String> starNames = new Vector<String>();

		ResourceBundle bundle
			= ResourceBundle.getBundle("engine.stars.StarResources", Locale.getDefault());

		int index = 1;
		while (true) {
			try {
				/* Проверяем есть ли локализированное значение для ключа "star + i." */
				bundle.getString("star" + index);

				/* Добавляем в вектор ключ имени. */
				starNames.add("star" + index);
				index++;

			} catch (Exception e) {
				break;
			}
		}

		return starNames.isEmpty() ? null : starNames;
	}
}
